#include <stdlib.h>
#include <pthread.h>

#define NHASH 29
#define HASH(id) ((unsigned long)id % NHASH)

struct foo *fh[NHASH];

pthread_mutex_t hashlock= PTHREAD_MUTEX_INITIALIZER;

struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
    struct foo * f_next;    /* protected by hashlock */
};

struct foo * foo_alloc(int id) {    /* allocate the object */
    struct foo *fp;
    int idx; 

    if ((fp= malloc(sizeof(struct foo))) != NULL) {
        fp->f_id= id;
        fp->f_count= 1;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            return NULL;
        }
        idx= HASH(id);
        pthread_mutex_lock(&hashlock);
        fp->f_next= fh[idx];
        fh[idx]= fp;
        pthread_mutex_lock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        pthread_mutex_unlock(&fp->f_lock) ;
        return fp;
    }
    return NULL;
}

/**
 * 如果要解决 11_10 中 foo_hold 正要被 foo_rele 释放掉的 struct foo，应该只调用 foo_find
 * ，如此 foo_find 和 foo_rele 中的 hashlock 锁能够保证不会找到已经被释放掉的 struct foo
 * ，而 foo_rele 可以保证不会释放仍然 hold 的 struct foo (foo_rele 中的两次判断 f_count)
 */
void foo_hold(struct foo * fp) {
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

struct foo *foo_find(int id) {
    struct foo *fp;
    pthread_mutex_lock(&hashlock);
    for (fp= fh[HASH(id)]; fp != NULL; fp= fp->f_next) {
        if (fp->f_id == id) {
            foo_hold(fp);
            break; 
        }
    }
    pthread_mutex_unlock(&hashlock);
    return fp;
}

void foo_rele(struct foo *fp) {
    int idx= HASH(fp->f_id);
    struct foo *pt;
     
    /**
     * 这个锁主要是为 != 1 的情况(==1的情况重新上锁并重新判断了)，因为有修改，显然要加锁 
     */
    pthread_mutex_lock(&fp->f_lock);
    if (fp->f_count == 1) {
        /* in order to avoid dead lock we cant to get mutex of hashlock directly。*/
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_lock(&hashlock);
        pthread_mutex_lock(&fp->f_lock);
        /* we must check again, as we unlock fp->f_lock */
        if (fp->f_count != 1) {
            fp->f_count--;
            pthread_mutex_unlock(&fp->f_lock);
            pthread_mutex_unlock(&hashlock);
            return;
        }  
        if (fh[idx] == fp) {
            fh[idx]= fp->f_next;
        } else {
            pt= fh[idx];
            while (pt->f_next != fp)
                pt= pt->f_next;
            pt->f_next= fp->f_next;
        }
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    } else {
        fp->f_count--;
        pthread_mutex_unlock(&fp->f_lock);
    }
}


